/**
 * AST 解析网址： https://astexplorer.net/
 * 
 * compiler-dom 核心：
 * template -> render 函数 3个步骤： 
 *  1,把 html 解析成 AST 语法树（AST 语法树描述的是语法本身）
 *  2,转化 ATS 语法树，加标记和进行优化（transform）
 *  3,生成 render 函数（核心字符串的拼接）
 */

export const enum NodeTypes {
  ROOT,
  ElEMENT,
  TEXT,
  SIMPLE_EXPRESSION = 4,
  INTERPOLATION = 5,
  ATTRIBUTE = 6,
  DIRECTIVE = 7,
  COMPOUND_EXPRESSION = 8,
  TEXT_CALL = 12,
  VNODE_CALL = 13,
}

/**
 * 解析是否完成，判断 context.source 是否为空
 * @param context 
 * @returns 
 */
function isEnd(context) {
  const source = context.source;
  if(source.startsWith('</')){
    return true;
  }
  return !source;
}

function advanceSpaces(context) {
  // 空格
  const match = /^[ \t\r\n]+/.exec(context.source);
  if (match) {
    advanceBy(context, match[0].length);
  }
}

/**
 * 解析标签
 * @param context 不断截取的模板内容
 */
 function parseTag(context){
  const start = getCursor(context); //<div/>
  // 最基本的元字符
  const match = /^<\/?([a-z][^ \t\r\n/>]*)/.exec(context.source); // igm
  const tag = match[1];
  advanceBy(context, match[0].length);
  advanceSpaces(context);

  // 判断是否是自闭合标签 <br /> 这种的
  const isSelfClosing = context.source.startsWith('/>');
  advanceBy(context, isSelfClosing ? 2 : 1);
  return {
      type:NodeTypes.ElEMENT,
      tag,
      isSelfClosing,
      loc:getSelection(context,start)
  }
}

function parseElement(context){
  // console.log('处理元素', context)
  // 解析标签名
  const ele: any = parseTag(context); // <div  ></div>
  
  // 处理儿子的时候，有可能没有儿子，那就直接跳出. 如果遇到结束标签就直接跳出
  const children = parseChildren(context);

  if(context.source.startsWith('</')){
    parseTag(context); // 解析结束标签时，同时删除 '/>', 并且更新偏移量
  }

  // 如果标签上还有属性，需要再处理，这里没有处理 ？？？？？？？？？？？？

  ele.children = children;
  ele.loc = getSelection(context, ele.loc.start);
  return ele;
}

function parseInterpolation(context){
  // console.log('处理表达式', context)
  // 获取表达式开始位置
  const start = getCursor(context);
  const closeIndex = context.source.indexOf('}}', '{{');

  advanceBy(context, 2); // 2 表示 {{ 后的位置
  const innerStart = getCursor(context); // 获取 name}} 开始位置
  const innerEnd = getCursor(context);
  const rawContentLength = closeIndex - 2; // 获取  name }}，包含空格的长度
  
  const preTrimContent = parseTextData(context, rawContentLength);
  const content = preTrimContent.trim(); // 取出大括号内元素的前后空格
  const startOffset = preTrimContent.indexOf(content);
  
  // 如果有前空格
  if (startOffset > 0) {
    advancePositionWithMutation(innerStart, preTrimContent, startOffset);
  }
  const endOffset = content.length + startOffset;
  advancePositionWithMutation(innerEnd, preTrimContent, endOffset);
  
  advanceBy(context, 2); // 干掉后面 }}

  return {
    type: NodeTypes.INTERPOLATION,
    content: {
      type: NodeTypes.SIMPLE_EXPRESSION,
      isStatic: false,
      loc: getSelection(context, innerStart, innerEnd),
       content
    },
    loc: getSelection(context, start)
  };
}

function getCursor(context) {
  let {
    line,
    colume,
    offset
  } = context;
  return {
    line,
    colume,
    offset
  }
}

function advancePositionWithMutation(context, s, endIndex) {
  let linesCount = 0; //  行
  let linePos = -1; // 记录换行后的第一个字符的位置
  for (let i = 0; i < endIndex; i++) {
    if (s.charCodeAt(i) == 10) { // \n 符号  '\n'.charCodeAt() === 10
      linesCount++; // 遇到换行符号就 ++
      linePos = i; // 换行后的第一个字符的位置
    }
  }
  // 偏移量
  context.offset += endIndex;
  //  行
  context.line += linesCount;
  // 列
  context.colume = linePos === -1 ? context.colume + endIndex : endIndex - linePos;
}

// 截取
function advanceBy(context, endIndex) {
  let s = context.source;
  // 计算新的结束位置
  advancePositionWithMutation(context, s, endIndex);
  context.source = s.slice(endIndex);
}

function parseTextData(context, endIndex) {
  const rawText = context.source.slice(0, endIndex);
  advanceBy(context, endIndex); // 不断截取
  return rawText;
}
function getSelection(context, start, end?) {
  end = getCursor(context);
  return {
    start,
    end,
    source: context.originalSource.slice(start.offset, end.offset)
  }
}
function parseText(context){
  // console.log('处理文本', context)
  const endTokens = ['<', '{{'];
  let endIndex = context.source.length;
  for (let i = 0; i < endTokens.length; i++) {
    const index = context.source.indexOf(endTokens[i], 1);
    if (index !== -1 && endIndex > index) {
      endIndex = index;
    }
  }
  // 更新行列信息
  let start = getCursor(context);
  const content = parseTextData(context, endIndex);
  
  return {
    type: NodeTypes.TEXT,
    loc: getSelection(context, start),
    content
  };
}

function parseChildren(context) {
  const nodes = [];

  // 不断截取模板，直至为空结束
  while (!isEnd(context)) {
    const s = context.source; // 当前上下文中的内容
    // 这里只讨论 标签<div></div>， 内容abc， 表达式{{}}，不考虑注释和自闭标签等

    let node;
    // 判断第 0 位置是否是 < 表示是标签
    if (s[0] === '<') {
      // 处理元素
      node = parseElement(context);
    } else if(s.startsWith('{{')){
      // 处理表达式
      node = parseInterpolation(context);
    } else {
      // 处理文本
      node = parseText(context);
    }
    
    nodes.push(node);
  }

  // 处理一堆空格，html页面多个空格也就一个，不是 &nbsp; 的那种空格，编辑器的那种
  nodes.forEach((node,index)=>{
    if(node.type === NodeTypes.TEXT){
      if(!/[^ \t\r\n]/.test(node.content)){ // 只要没有内容，就删除掉
        nodes[index] = null
      }else{
        node.content = node.content.replace(/[ \t\r\n]+/g,' ')
      }
    }
  })
  return nodes.filter(Boolean); // 过滤null值
}

function createParserContext(content){
  
  return {
    line: 1,
    colume: 1,
    offset: 0,
    source: content, // 不断截取，直到为空为止
    originalSource: content, // 原始模板内容
  }
  
}

// 用户编写可以多个根节点，不用像 vue2 那样自己包裹一个根元素包裹
function createRoot(children, loc){
  return {
    type: NodeTypes.ROOT,
    children,
    loc
  }
}

/**
 * 将模板转成 AST 语法树
 * @param content 用户的 template 模板
 */
function baseParse(content) {
  // 标识节点的信息，行、列、偏移量 ... 对模板的解析
  const context = createParserContext(content);
  const start = getCursor(context); // 记录开始位置
  return createRoot(parseChildren(context), getSelection(context, start));
}

export function baseCompile(template) {
  // 将模板转成 AST 语法树
  const ast = baseParse(template);
  return ast;
}

